#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <malloc.h>

#include "ft_arq.h"
#include "ft_proto.h"
#include "ft_timers.h"

extern int ft_debug;
extern int ft_speed;

void ft_arq_setup(struct ft_arq_t* arq,int fd)
{
  //init for random number generation
  srand(time(NULL));
  
  arq->sockfd = fd;
  arq->winsz = 8;
  arq->rtt = 70000;
  arq->nx_seq_num = rand()^rand();
  arq->pkt_size = 1380;
  arq->state = CON_DISCON;
}

int ft_arq_send_file(struct ft_arq_t* arq, FILE* file)
{
  struct ft_packet_t* pkt;
 
  //setup
  char* pktbuff;
  unsigned char ackpkt[ACK_PCKT_SIZE+PKT_HEADER_SIZE];
  int ack_size=0, pkt_size=0, i=0, size=0, retries=30;
  int cur_wsz = 0;
  int rtti=0;
  
  //my timer struction
  struct ft_timer_t timer;
  
  //we should be disconnected
  if (arq->state != CON_DISCON)
    return -1;

  arq->state = CON_READY;
  
  //allocate and zero
  pkt = (struct ft_packet_t*)calloc(arq->winsz,sizeof(*pkt));
  for (i = 0; i < arq->winsz; i++)
    pkt[i].data = (char*)malloc(arq->pkt_size);

  while (!feof(file) && arq->state != CON_DONE)
  {
    //set tmp variables
    cur_wsz = 0;
    retries = 30;
    for (i = 0; i < arq->winsz; i++)
    {
      if (arq->state == CON_READY)
      {
        pkt[i].flags = PKT_START | PKT_DATA;
        arq->state = CON_BUSY;
      }
      else
        pkt[i].flags = PKT_DATA;
      
      pkt[i].seq_num = arq->nx_seq_num++;
      pkt[i].wstart = ftell(file);

      if (ft_debug) printf("Queuing up packets to send -- %d\n",pkt[i].seq_num);
      //read data from file
      size = fread(pkt[i].data,sizeof(char),arq->pkt_size,file);
      if (size < arq->pkt_size && !feof(file))
      {
        fprintf(stderr,"%s:%d -- I/O error reading file.\n",__FILE__,__LINE__);
        ferror(file);
        return -1;
      }
      else
      {
        //increment windox size (we might not fill it entirely)
        cur_wsz++;
        pkt[i].length = size;
        if (feof(file))
        {
          pkt[i].flags |= PKT_END;
          arq->state = CON_DONE;
          break;
        }
      }
    }

    //how many times the timer can time out
    while (retries > 0)
    {
      //begin sending window
      if (ft_debug) printf("Sending Window\n");
      for (i = 0; i < cur_wsz; i++)
      {
        //make sure this is a valid packet to send
        if ((pkt[i].flags & PKT_ACK) == 0 && pkt[i].flags != 0)
        {
          //generate the packet
          pktbuff = ft_gen_packet(&pkt[i],&pkt_size);
          //send the packet
          if (send(arq->sockfd,pktbuff,pkt_size,0) != pkt_size)
          {
            fprintf(stderr,"%s:%d -- Invalid packet sent length.\n",__FILE__,__LINE__);
            perror("");
            return -1;
          }
          if (ft_debug) printf("Sending packets just Queued -- %d\n",pkt[i].seq_num);
          free(pktbuff);
        }
      }

      //setup polling stuff
      struct pollfd pfd;
      pfd.fd = arq->sockfd;
      pfd.events = POLLIN|POLLPRI; 
 
      //set the timer
      set_ft_timer(&timer,arq->rtt);
      while (check_ft_timer(&timer) > 0)
      {
        //use poll to timeout
        if (poll(&pfd,1,arq->rtt/250) > 0)
          ack_size = recv(arq->sockfd,ackpkt,ACK_PCKT_SIZE,0);
        if (ack_size > 0)
        {
          struct ft_packet_t* ack;
          ack = ft_unpack_packet(ackpkt,ack_size);
          ack_size = 0;
          size = 0;

          if (ft_debug) printf("Got some type of packet\n");

          //make sure this is a valid reception packet
          if ((ack->flags & PKT_ACK) == PKT_ACK || (ack->flags & PKT_NACK) == PKT_NACK)
          {
            for (i = 0; i < cur_wsz; i++)
            {
              if (ack->seq_num == pkt[i].seq_num && ack->wstart == pkt[i].wstart)
              {
                //packet was a good ack, lets fill the window back up
                if ((ack->flags & PKT_ACK) == PKT_ACK)
                {
                  if (ft_debug) printf("Just got ack for %d\n",pkt->seq_num);
                  
                  if (!feof(file) && arq->state != CON_DONE)
                  {
                    pkt[i].flags = PKT_DATA;
                    pkt[i].seq_num = arq->nx_seq_num++;
                    pkt[i].wstart = ftell(file);

                    if (ft_debug) 
                      printf("Queuing up packets to send -- %d\n",pkt[i].seq_num);
              
                    size = fread(pkt[i].data,sizeof(char),arq->pkt_size,file);
                    if (size < arq->pkt_size && !feof(file))
                    {
                      fprintf(stderr,"%s:%d -- I/O error reading file.\n"
                          ,__FILE__,__LINE__);
                      ferror(file);
                      return -1;
                    }
                    else
                    {
                      pkt[i].length = size;
                      if (feof(file))
                      {
                        pkt[i].flags |= PKT_END;
                        arq->state = CON_DONE;
                        break;
                      }
                    }
                  }
                  else
                    pkt[i].flags |= PKT_ACK;
                }
                else
                  if (ft_debug) printf("Just got nack for %d\n",pkt->seq_num);
                /* RTT calculation */
                rtti = delta_ft_timer(&timer);
                arq->rtt = 0.85*arq->rtt + 0.15F*rtti;
                set_ft_timer(&timer,arq->rtt);
                retries = 30;
                if (ft_debug) printf("New RTT: %d\n",arq->rtt);
              }
              if ((pkt[i].flags & PKT_ACK) == PKT_ACK)
                size++;
            }
            if (size == cur_wsz)
            {
              free(ack);
              break;
            }
          }
          else
            free(ack);
        }
      }
      //the timer timedout totally
      if (check_ft_timer(&timer) <= 0)
      {
        if (ft_debug) fprintf(stdout,"%s:%d -- recv timeout -- trying again\n",__FILE__,__LINE__);
        retries--;
        //increase rtt
        arq->rtt = 0.85*arq->rtt + 0.5*arq->rtt;
      }
      else
        break;
    }
    if (retries <= 0)
    {
      fprintf(stderr,"Alot of Timeouts -- are you sure the peer is up?\n");
      return -1;
    }
  }
  if (feof(file) && arq->state == CON_DONE)
  {
    //begin sending final packets for dicson
    struct ft_packet_t fin_pkt;
    char* buff;
    int size = 0;
    fin_pkt.flags = PKT_DISCON;
    fin_pkt.seq_num = arq->nx_seq_num++;
    fin_pkt.length = 0;
    
    buff = ft_gen_packet(&fin_pkt,&size);
    send(arq->sockfd,buff,size,0);
    usleep(10000);
    send(arq->sockfd,buff,size,0);
    usleep(10000);
    send(arq->sockfd,buff,size,0);
    return 1;
  } 
  else 
    return -1;
}

int ft_arq_recv_file(struct ft_arq_t* arq, FILE* file)
{
  //setup basic stuff
  struct ft_packet_t ack_pkt;
  struct ft_packet_t* pkt;
  char *pktbuff;
  unsigned char ackpkt[ACK_PCKT_SIZE];
  int ack_size=0, pkt_size=0, rtti=0, stime=0;

  struct ft_timer_t timer;
  struct ft_timer_t main_timer;
  int retries = 30;
  int recv_bytes = 0;

  stime = time(NULL);  

  if (arq->state != CON_DISCON)
    return -1;

  arq->state = CON_READY;

  //setup timers
  set_ft_timer(&main_timer,0);
  
  //make sure we start connected
  while (arq->state != CON_DISCON)
  {
    //setup poll structions
    struct pollfd pfd;
    pfd.fd = arq->sockfd;
    pfd.events = POLLIN|POLLPRI;

    retries = 30;
    while (retries > 0)
    {
      //set timer to timeout
      set_ft_timer(&timer,arq->rtt);
      while( check_ft_timer(&timer) > 0)
      {
        //setup poll to timeout on wait
        if (poll(&pfd,1,arq->rtt/250) > 0 && 
            (pkt_size = recv(arq->sockfd,ackpkt,ACK_PCKT_SIZE,0)) > 0)
        {
          /* RTT calculation */
          rtti = delta_ft_timer(&timer);
          arq->rtt = (0.85 * arq->rtt) + 0.15*rtti;
          set_ft_timer(&timer,arq->rtt);
          retries = 30;
          break;
        }
      }
      //make sure we got something and not timeout
      if (pkt_size > 0)
      {
        if (ft_debug) printf("Got a Packet -- size %d\n",pkt_size);
        break;
      }
      else 
      {
        retries--;
        arq->rtt = 0.85*arq->rtt + 0.5*arq->rtt;
      }
    }
    if (retries == 0)
    {
      fprintf(stderr,"%s:%d -- Alot of recv timeouts... is the peer up?\n"
          ,__FILE__,__LINE__);
      return -1;
    }

    //unpacket what we just recieved
    pkt = ft_unpack_packet(ackpkt,pkt_size);
    pkt_size = 0;
    if (pkt != NULL)
    {
      //data |start handler
      if (pkt->flags == PKT_DATA || pkt->flags == (PKT_DATA | PKT_START))
      {
        if (ft_debug) printf("It was a Data packet\n");
        ack_pkt.flags = PKT_ACK;
        ack_pkt.seq_num = pkt->seq_num;
        ack_pkt.wstart = pkt->wstart;
        ack_pkt.length = 0;
        arq->state = CON_BUSY;
      }
      //data end handler
      else if (pkt->flags == (PKT_DATA | PKT_END))
      {
        if (ft_debug) printf("It was a data packet, but the end of the stream\n");
        ack_pkt.flags = PKT_ACK | PKT_END;
        ack_pkt.seq_num = pkt->seq_num;
        ack_pkt.wstart = pkt->wstart;
        ack_pkt.length = 0;
        arq->state = CON_DONE;
      }
      //failed checksum handler
      else if (pkt->flags == PKT_FCKSM)
      {
        if (ft_debug) printf("Failed the checksum\n");
        ack_pkt.flags = PKT_NACK;
        ack_pkt.seq_num = pkt->seq_num;
        ack_pkt.wstart = pkt->wstart;
        ack_pkt.length = 0;
      }
      //end handler
      else if (pkt->flags == PKT_END)
      {
        if (ft_debug) printf("Says stream is done\n");
        arq->state = CON_DONE;
        ack_pkt.flags = PKT_END;
        ack_pkt.seq_num = pkt->seq_num;
        ack_pkt.length = 0;
      }
      //end-data-start handler
      else if (pkt->flags == (PKT_END | PKT_DATA | PKT_START))
      {
        if (ft_debug) printf("Data Start End Packet\n");
        ack_pkt.flags = PKT_ACK | PKT_END;
        ack_pkt.seq_num = pkt->seq_num;
        ack_pkt.wstart = pkt->wstart;
        ack_pkt.length = 0;
        arq->state = CON_DONE;
      }
      //disconnect handler
      else if ((pkt->flags & PKT_DISCON) == PKT_DISCON)
      {
        arq->state = CON_DISCON;
        ack_pkt.flags = PKT_FCKSM;
        if (ft_debug) printf("And we're done.\n");
        break;
      }
      //failure handler
      else
      {
        if (ft_debug) printf("Packet had weird flags %X... lets force the checksum to be bad.\n",pkt->flags);
        ack_pkt.flags = PKT_FCKSM;
      }
    }
    else
      ack_pkt.flags = PKT_FCKSM;

    //make sure checksum wasn't bad
    if ((ack_pkt.flags & PKT_FCKSM) != PKT_FCKSM)
    {
      if ((ack_pkt.flags & PKT_ACK) == PKT_ACK)
      {
        //move to write position
        if (fseek(file,pkt->wstart,SEEK_SET) != 0)
        {
          fprintf(stderr,"%s:%d -- I/O Seek error Occured.\n",__FILE__,__LINE__);
          ferror(file);
          return -1;
        }
        //then write the packets data
        if (fwrite(pkt->data,sizeof(*pkt->data),pkt->length,file) != pkt->length)
        {
          fprintf(stderr,"%s:%d -- I/O write error Occured.\n",__FILE__,__LINE__);
          ferror(file);
          return -1;
        }
        else
        {
          if (ft_debug) printf("Writing stream to file at %d for %d\n",(int)pkt->wstart,pkt->length);
          //keep track of valid data written
          recv_bytes += pkt->length;
          if (pkt->length > 0)
            free(pkt->data);
          free(pkt);
        }
      }
      //generate ACK and send
      pktbuff = ft_gen_packet(&ack_pkt,&ack_size);
      if (send(arq->sockfd,pktbuff,ack_size,0) != ack_size)
      {
        fprintf(stderr,"%s:%d -- Invalid packet sent length.\n",__FILE__,__LINE__);
        perror("");
        return -1;
      }
    }
  }
  //if wanted print projected speed pf transfer
  if (ft_debug || ft_speed) printf("Speed: %d Bps\n",(int)(((double)recv_bytes/(double)delta_ft_timer(&main_timer))*1000000));
  return 1;
}
